{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 安装类库\n",
    "# !mkdir /home/aistudio/external-libraries\n",
    "# !pip install imgaug -t /home/aistudio/external-libraries\n",
    "import sys\n",
    "sys.path.append('/home/aistudio/external-libraries')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: (32, 32, 3)\n",
      "label value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 读取数据\n",
    "reader = paddle.batch(\n",
    "    paddle.dataset.cifar.train10(),\n",
    "    batch_size=8) # 数据集读取器\n",
    "data = next(reader()) # 读取数据\n",
    "index = 4 # 批次索引\n",
    "\n",
    "# 读取图像\n",
    "image = np.array([x[0] for x in data]).astype(np.float32) # 读取图像数据，数据类型为float32\n",
    "image = image * 255 # 从[0,1]转换到[0,255]\n",
    "image = image[index].reshape((3, 32, 32)).transpose((1, 2, 0)).astype(np.uint8) # 数据格式从CHW转换为HWC，数据类型转换为uint8\n",
    "print('image shape:', image.shape)\n",
    "\n",
    "# 图像增强\n",
    "# sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "# seq = iaa.Sequential([\n",
    "#     sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "#     iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "# image = seq(image=image)\n",
    "\n",
    "# 读取标签\n",
    "label = np.array([x[1] for x in data]).astype(np.int64) # 读取标签数据，数据类型为int64\n",
    "vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "print('label value:', vlist[label[index]])\n",
    "\n",
    "# 显示图像\n",
    "image = Image.fromarray(image)   # 转换图像格式\n",
    "image.save('./work/out/img.png') # 保存读取图像\n",
    "plt.figure(figsize=(3, 3))       # 设置显示大小\n",
    "plt.imshow(image)                # 设置显示图像\n",
    "plt.show()                       # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n",
      "valid_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 训练数据增强\n",
    "def train_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 增强图像\n",
    "    sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "    seq = iaa.Sequential([\n",
    "        sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "        iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "    images = seq(images=images)\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 验证数据增强\n",
    "def valid_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 读取训练数据\n",
    "train_reader = paddle.batch(\n",
    "    paddle.reader.shuffle(paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "train_data = next(train_reader()) # 读取训练数据\n",
    "\n",
    "train_image = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取训练图像\n",
    "train_image = train_augment(train_image)                                                       # 训练图像增强\n",
    "train_label = np.array([x[1] for x in train_data]).reshape((-1, 1)).astype(np.int64)           # 读取训练标签\n",
    "print('train_data: image shape {}, label shape:{}'.format(train_image.shape, train_label.shape))\n",
    "\n",
    "# 读取验证数据\n",
    "valid_reader = paddle.batch(\n",
    "    paddle.dataset.cifar.test10(),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "valid_data = next(valid_reader()) # 读取验证数据\n",
    "\n",
    "valid_image = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取验证图像\n",
    "valid_image = valid_augment(valid_image)                                                       # 验证图像增强\n",
    "valid_label = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64)           # 读取验证标签\n",
    "print('valid_data: image shape {}, label shape:{}'.format(valid_image.shape, valid_label.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear, BatchNorm\n",
    "import math\n",
    "\n",
    "# 模组结构：输入维度，输出维度，滑动步长，基础长度\n",
    "group_arch = [(16, 16, 1, 9), (16, 32, 2, 9), (32, 64, 2, 9)]\n",
    "group_dim  = 64 # 模组输出维度\n",
    "class_dim  = 10 # 类别数量维度\n",
    "\n",
    "# 卷积单元\n",
    "class ConvUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=3, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化卷积单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ConvUnit, self).__init__()\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=filter_size,\n",
    "            stride=stride,\n",
    "            padding=(filter_size-1)//2,                       # 输出特征图大小不变\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 分割结构\n",
    "class HSBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, splits=5, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始HS-Block结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长，1保持不变，2下采样\n",
    "            splits  - 分割次数\n",
    "            act     - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(HSBlock, self).__init__()\n",
    "        \n",
    "        # 计算通道\n",
    "        channel0 = out_dim // splits\n",
    "        channel1 = channel0 * 2\n",
    "        channel2 = channel0 * splits\n",
    "        \n",
    "        # 特征平分\n",
    "        self.conv1 = ConvUnit(in_dim=in_dim, out_dim=channel2, filter_size=1, stride=1, act=act)\n",
    "        \n",
    "        # 特征升维\n",
    "        self.conv2 = ConvUnit(in_dim=channel0, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        \n",
    "        # 重复合并\n",
    "        self.conv3 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        self.conv4 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        self.conv5 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        \n",
    "        # 合并特征\n",
    "        self.conv6 = ConvUnit(in_dim=channel2 + channel0, out_dim=out_dim, filter_size=1, stride=1, act=act)\n",
    "            \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 特征平分\n",
    "        x = self.conv1(x)\n",
    "        x0, x1, x2, x3, x4 = fluid.layers.split(input=x, num_or_sections=5, dim=1)\n",
    "        \n",
    "        # 特征升维\n",
    "        x1 = self.conv2(x1)\n",
    "        x1_0, x1_1 = fluid.layers.split(input=x1, num_or_sections=2, dim=1)\n",
    "        \n",
    "        # 重复合并\n",
    "        x2 = fluid.layers.concat(input=[x2, x1_1], axis=1)\n",
    "        x2 = self.conv3(x2)\n",
    "        x2_0, x2_1 = fluid.layers.split(input=x2, num_or_sections=2, dim=1)\n",
    "        \n",
    "        x3 = fluid.layers.concat(input=[x3, x2_1], axis=1)\n",
    "        x3 = self.conv4(x3)\n",
    "        x3_0, x3_1 = fluid.layers.split(input=x3, num_or_sections=2, dim=1)\n",
    "        \n",
    "        x4 = fluid.layers.concat(input=[x4, x3_1], axis=1)\n",
    "        x4 = self.conv5(x4)\n",
    "        \n",
    "        # 合并特征\n",
    "        x = fluid.layers.concat(input=[x0, x1_0, x2_0, x3_0, x4], axis=1)\n",
    "        x = self.conv6(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 基础结构\n",
    "class ResBasic(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, is_pass=True):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化基础结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            is_pass - 是否直连\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBasic, self).__init__()\n",
    "        \n",
    "        # 是否直连标识\n",
    "        self.is_pass = is_pass\n",
    "        \n",
    "        # 添加投影路径\n",
    "        self.proj = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=1, stride=stride, act=None)\n",
    "        \n",
    "        # 添加卷积路径\n",
    "        self.con1 = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=3, stride=stride, act='relu')\n",
    "        self.con2 = HSBlock(in_dim=out_dim, out_dim=out_dim, stride=1, splits=5, act='relu')\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "            y - 输出特征\n",
    "        \"\"\"\n",
    "        # 直连路径\n",
    "        if self.is_pass: # 是否直连\n",
    "            x_pass = x\n",
    "        else:            # 否则投影\n",
    "            x_pass = self.proj(x)\n",
    "        \n",
    "        # 卷积路径\n",
    "        x_con1 = self.con1(x)\n",
    "        x_con2 = self.con2(x_con1)\n",
    "        \n",
    "        # 输出特征\n",
    "        x = fluid.layers.elementwise_add(x=x_pass, y=x_con1, act='relu') # 直连路径与卷积路径进行特征相加\n",
    "        \n",
    "        return x\n",
    "    \n",
    "# 模块结构\n",
    "class ResBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, basics=1):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模块结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            basics  - 基础长度\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBlock, self).__init__()\n",
    "        \n",
    "        # 添加模块列表\n",
    "        self.block_list = [] # 模块列表\n",
    "        for i in range(basics):\n",
    "            block_item = self.add_sublayer( # 构造模块项目\n",
    "                'block_' + str(i),\n",
    "                ResBasic(\n",
    "                    in_dim=(in_dim if i==0 else out_dim), # 每组模块项目除第一块外，输入维度=输出维度\n",
    "                    out_dim=out_dim,\n",
    "                    stride=(stride if i==0 else 1), # 每组模块项目除第一块外，stride=1\n",
    "                    is_pass=(False if i==0 else True))) # 每组模块项目除第一块外，is_pass=True\n",
    "            self.block_list.append(block_item) # 添加模块项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for block_item in self.block_list:\n",
    "            x = block_item(x) # 提取模块特征\n",
    "            \n",
    "        return x\n",
    "\n",
    "# 模组结构\n",
    "class ResGroup(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模组结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResGroup, self).__init__()\n",
    "        \n",
    "        # 添加模组列表\n",
    "        self.group_list = [] # 模组列表\n",
    "        for i, block_arch in enumerate(group_arch):\n",
    "            group_item = self.add_sublayer( # 构造模组项目\n",
    "                'group_' + str(i),\n",
    "                ResBlock(\n",
    "                    in_dim=block_arch[0],\n",
    "                    out_dim=block_arch[1],\n",
    "                    stride=block_arch[2],\n",
    "                    basics=block_arch[3]))\n",
    "            self.group_list.append(group_item) # 添加模组项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for group_item in self.group_list:\n",
    "            x = group_item(x) # 提取模组特征\n",
    "            \n",
    "        return x\n",
    "        \n",
    "# 残差网络\n",
    "class ResNet(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化残差网络，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResNet, self).__init__()\n",
    "        \n",
    "        # 添加初始化层\n",
    "        self.conv = ConvUnit(in_dim=3, out_dim=16, filter_size=3, stride=1, act='relu')\n",
    "        \n",
    "        # 添加模组结构\n",
    "        self.backbone = ResGroup() # 输出：N*C*H*W\n",
    "        \n",
    "        # 添加全连接层\n",
    "        self.pool = Pool2D(global_pooling=True, pool_type='avg') # 输出：N*C*1*1\n",
    "        \n",
    "        stdv = 1.0/(math.sqrt(group_dim)*1.0)                    # 设置均匀分布权重方差\n",
    "        self.fc = Linear(                                        # 输出：=N*10\n",
    "            input_dim=group_dim,\n",
    "            output_dim=class_dim,\n",
    "            param_attr=fluid.initializer.Uniform(-stdv, stdv),   # 使用均匀分布初始权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),           # 使用常量数值初始偏置\n",
    "            act='softmax')\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入图像进行分类\n",
    "        输入:\n",
    "            x - 输入图像\n",
    "        输出:\n",
    "            x - 预测结果\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x = self.conv(x)\n",
    "        x = self.backbone(x)\n",
    "        \n",
    "        # 进行预测\n",
    "        x = self.pool(x)\n",
    "        x = fluid.layers.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tatol param: 795428\n",
      "infer shape: [1, 10]\n"
     ]
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.base import to_variable\n",
    "import numpy as np\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 输入数据\n",
    "    x = np.random.randn(1, 3, 32, 32).astype(np.float32)\n",
    "    x = to_variable(x)\n",
    "    \n",
    "    # 进行预测\n",
    "    backbone = ResNet() # 设置网络\n",
    "    \n",
    "    infer = backbone(x) # 进行预测\n",
    "    \n",
    "    # 显示结果\n",
    "    parameters = 0\n",
    "    for p in backbone.parameters():\n",
    "        parameters += np.prod(p.shape) # 统计参数\n",
    "    \n",
    "    print('tatol param:', parameters)\n",
    "    print('infer shape:', infer.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete - train time: 19626s, best epoch: 283, best loss: 0.265734, best accuracy: 92.86%\r"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.utils.plot import Ploter\n",
    "import numpy as np\n",
    "import time\n",
    "import math\n",
    "import os\n",
    "\n",
    "epoch_num = 300   # 训练周期，取值一般为[1,300]\n",
    "train_batch = 128 # 训练批次，取值一般为[1,256]\n",
    "valid_batch = 128 # 验证批次，取值一般为[1,256]\n",
    "displays = 100    # 显示迭代\n",
    "\n",
    "start_lr = 0.00001                         # 开始学习率，取值一般为[1e-8,5e-1]\n",
    "based_lr = 0.1                             # 基础学习率，取值一般为[1e-8,5e-1]\n",
    "epoch_iters = math.ceil(50000/train_batch) # 每轮迭代数\n",
    "warmup_iter = 10 * epoch_iters             # 预热迭代数，取值一般为[1,10]\n",
    "\n",
    "momentum = 0.9     # 优化器动量\n",
    "l2_decay = 0.00005 # 正则化系数，取值一般为[1e-5,5e-4]\n",
    "epsilon = 0.05     # 标签平滑率，取值一般为[1e-2,1e-1]\n",
    "\n",
    "checkpoint = False                   # 断点标识\n",
    "model_path = './work/out/hs-resnet'  # 模型路径\n",
    "result_txt = './work/out/result.txt' # 结果文件\n",
    "class_num  = 10                      # 类别数量\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 准备数据\n",
    "    train_reader = paddle.batch(\n",
    "        reader=paddle.reader.shuffle(reader=paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "        batch_size=train_batch)\n",
    "    \n",
    "    valid_reader = paddle.batch(\n",
    "        reader=paddle.dataset.cifar.test10(),\n",
    "        batch_size=valid_batch)\n",
    "    \n",
    "    # 声明模型\n",
    "    model = ResNet()\n",
    "    \n",
    "    # 优化算法\n",
    "    consine_lr = fluid.layers.cosine_decay(based_lr, epoch_iters, epoch_num) # 余弦衰减策略\n",
    "    decayed_lr = fluid.layers.linear_lr_warmup(consine_lr, warmup_iter, start_lr, based_lr) # 线性预热策略\n",
    "    \n",
    "    optimizer = fluid.optimizer.Momentum(\n",
    "        learning_rate=decayed_lr,                           # 衰减学习策略\n",
    "        momentum=momentum,                                  # 优化动量系数\n",
    "        regularization=fluid.regularizer.L2Decay(l2_decay), # 正则衰减系数\n",
    "        parameter_list=model.parameters())\n",
    "    \n",
    "    # 加载断点\n",
    "    if checkpoint: # 是否加载断点文件\n",
    "        model_dict, optimizer_dict = fluid.load_dygraph(model_path) # 加载断点参数\n",
    "        model.set_dict(model_dict)                                  # 设置权重参数\n",
    "        optimizer.set_dict(optimizer_dict)                          # 设置优化参数\n",
    "    else:          # 否则删除结果文件\n",
    "        if os.path.exists(result_txt): # 如果存在结果文件\n",
    "            os.remove(result_txt)      # 那么删除结果文件\n",
    "    \n",
    "    # 初始训练\n",
    "    avg_train_loss = 0 # 平均训练损失\n",
    "    avg_valid_loss = 0 # 平均验证损失\n",
    "    avg_valid_accu = 0 # 平均验证精度\n",
    "    \n",
    "    iterator = 1                                # 迭代次数\n",
    "    train_prompt = \"Train loss\"                 # 训练标签\n",
    "    valid_prompt = \"Valid loss\"                 # 验证标签\n",
    "    ploter = Ploter(train_prompt, valid_prompt) # 训练图像\n",
    "    \n",
    "    best_epoch = 0           # 最好周期\n",
    "    best_accu = 0            # 最好精度\n",
    "    best_loss = 100.0        # 最好损失\n",
    "    train_time = time.time() # 训练时间\n",
    "    \n",
    "    # 开始训练\n",
    "    for epoch_id in range(epoch_num):\n",
    "        # 训练模型\n",
    "        model.train() # 设置训练\n",
    "        for batch_id, train_data in enumerate(train_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = train_augment(image_data)                                                        # 使用数据增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "\n",
    "            label_data = np.array([x[1] for x in train_data]).astype(np.int64)                        # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                             # 转换数据类型\n",
    "            label = fluid.layers.label_smooth(label=fluid.one_hot(label, class_num), epsilon=epsilon) # 使用标签平滑\n",
    "            label.stop_gradient = True                                                                # 停止梯度传播\n",
    "\n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label, soft_label=True)\n",
    "            train_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            # 反向传播\n",
    "            train_loss.backward()\n",
    "            optimizer.minimize(train_loss)\n",
    "            model.clear_gradients()\n",
    "            \n",
    "            # 显示结果\n",
    "            if iterator % displays == 0:\n",
    "                # 显示图像\n",
    "                avg_train_loss = train_loss.numpy()[0]                # 设置训练损失\n",
    "                ploter.append(train_prompt, iterator, avg_train_loss) # 添加训练图像\n",
    "                ploter.plot()                                         # 显示训练图像\n",
    "                \n",
    "                # 打印结果\n",
    "                print(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\".format(\n",
    "                    iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "                \n",
    "                # 写入文件\n",
    "                with open(result_txt, 'a') as file:\n",
    "                    file.write(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\\n\".format(\n",
    "                        iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "            \n",
    "            # 增加迭代\n",
    "            iterator += 1\n",
    "            \n",
    "        # 验证模型\n",
    "        valid_loss_list = [] # 验证损失列表\n",
    "        valid_accu_list = [] # 验证精度列表\n",
    "        \n",
    "        model.eval()   # 设置验证\n",
    "        for batch_id, valid_data in enumerate(valid_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = valid_augment(image_data)                                                        # 使用图像增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "            \n",
    "            label_data = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64) # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                       # 转换数据类型\n",
    "            label.stop_gradient = True                                                          # 停止梯度传播\n",
    "            \n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算精度\n",
    "            valid_accu = fluid.layers.accuracy(infer,label)\n",
    "            \n",
    "            valid_accu_list.append(valid_accu.numpy())\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label)\n",
    "            valid_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            valid_loss_list.append(valid_loss.numpy())\n",
    "        \n",
    "        # 设置结果\n",
    "        avg_valid_accu = np.mean(valid_accu_list)             # 设置验证精度\n",
    "        \n",
    "        avg_valid_loss = np.mean(valid_loss_list)             # 设置验证损失\n",
    "        ploter.append(valid_prompt, iterator, avg_valid_loss) # 添加训练图像\n",
    "        \n",
    "        # 保存模型\n",
    "        fluid.save_dygraph(model.state_dict(), model_path)     # 保存权重参数\n",
    "        fluid.save_dygraph(optimizer.state_dict(), model_path) # 保存优化参数\n",
    "        \n",
    "        if avg_valid_loss < best_loss:\n",
    "            fluid.save_dygraph(model.state_dict(), model_path + '-best') # 保存权重\n",
    "            \n",
    "            best_epoch = epoch_id + 1                                    # 更新迭代\n",
    "            best_accu = avg_valid_accu                                   # 更新精度\n",
    "            best_loss = avg_valid_loss                                   # 更新损失\n",
    "    \n",
    "    # 显示结果\n",
    "    train_time = time.time() - train_time # 设置训练时间\n",
    "    print('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}'.format(\n",
    "        train_time, best_epoch, best_loss, best_accu))\n",
    "    \n",
    "    # 写入文件\n",
    "    with open(result_txt, 'a') as file:\n",
    "        file.write('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}\\n'.format(\n",
    "            train_time, best_epoch, best_loss, best_accu))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "infer time: 0.077619s, infer value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import time\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image_path = './work/out/img.png' # 图片路径\n",
    "model_path = './work/out/hs-resnet-best' # 模型路径\n",
    "\n",
    "# 加载图像\n",
    "def load_image(image_path):\n",
    "    \"\"\"\n",
    "    功能:\n",
    "        读取图像并转换到输入格式\n",
    "    输入:\n",
    "        image_path - 输入图像路径\n",
    "    输出:\n",
    "        image - 输出图像\n",
    "    \"\"\"\n",
    "    # 读取图像\n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    \n",
    "    # 转换格式\n",
    "    image = image.resize((32, 32), Image.ANTIALIAS) # 调整图像大小\n",
    "    image = np.array(image, dtype=np.float32) # 转换数据格式，数据类型转换为float32\n",
    "\n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    image = (image/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    image = image.transpose((2, 0, 1)).astype(np.float32) # 数据格式从HWC转换为CHW，数据类型转换为float32\n",
    "    \n",
    "    # 增加维度\n",
    "    image = np.expand_dims(image, axis=0) # 增加数据维度\n",
    "    \n",
    "    return image\n",
    "\n",
    "# 预测图像\n",
    "with fluid.dygraph.guard():\n",
    "    # 读取图像\n",
    "    image = load_image(image_path)\n",
    "    image = fluid.dygraph.to_variable(image)\n",
    "    \n",
    "    # 加载模型\n",
    "    model = ResNet()                               # 加载模型\n",
    "    model_dict, _ = fluid.load_dygraph(model_path) # 加载权重\n",
    "    model.set_dict(model_dict)                     # 设置权重\n",
    "    model.eval()                                   # 设置验证\n",
    "    \n",
    "    # 前向传播\n",
    "    infer_time = time.time()              # 推断开始时间\n",
    "    infer = model(image)\n",
    "    infer_time = time.time() - infer_time # 推断结束时间\n",
    "    \n",
    "    # 显示结果\n",
    "    vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "    print('infer time: {:f}s, infer value: {}'.format(infer_time, vlist[np.argmax(infer.numpy())]) )\n",
    "    \n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    plt.figure(figsize=(3, 3))     # 设置显示大小\n",
    "    plt.imshow(image)              # 设置显示图像\n",
    "    plt.show()                     # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.8.4 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
